.. _глава_34_start:

Типы значения и ссылочные типы
===============================

.. index::
    типы значения
    ссылочные типы

В этой главе рассмотрим понятия типов значений и ссылочных типов. Эти
понятия особенно важны для понимания различий между структурами и классами.
В этой главе также более подробно рассматривается оператор &.
Глава заканчивается таблицей, содержащей результаты применения следующих двух
концепций для различных типов переменных:

* Сравнение значений
* Сравнение адресов

Типы значений
-------------

Типы значений легко описать: переменные типов значений переносят значения.
Например, все типы целых чисел и с плавающей запятой являются типами значений. Хотя это не
сразу очевидно, массивы фиксированной длины также являются типами значений.
Например, переменная типа int имеет целочисленное значение:

.. code-block:: d

    int speed = 123;

Количество байт, которое занимает переменная speed, равно размеру типа int. Если мы
представим память в виде ленты, идущей слева направо, мы можем представить , что переменная живет в какой-то ее части:


.. image:: images/speed_34.1_1.png


Когда переменные типов значений копируются, они получают свои собственные значения:

.. code-block:: d

    int newSpeed = speed;

Новая переменная будет иметь свое собственное место и значение:

.. image:: images/speed_newspeed_34.1_2.png

Естественно, изменения, вносимые в эти переменные, являются независимыми:

.. code-block:: d

    speed = 200;

Значение другой переменной не изменяется:

.. image:: images/speed_newspeed_34.1_3.png


**Использование проверок assert**

Следующие примеры содержат проверки assert, указывающие на то, что их условия
верны. Другими словами, это не проверки в обычном смысле, скорее мой способ
сообщить читателю, что "это правда".Например, проверка assert(speed == newSpeed) ниже означает "скорость
равна newSpeed".

**Идентичность значений**

Как видно из приведенных выше представлений памяти, существует два типа равенства, которые относятся к переменным:

* Равенство значений: Оператор ==, который встречается во многих примерах по всей
  книге, сравнивает переменные по их значениям. Когда говорят, что две переменные
  равны в этом смысле, их значения равны.
* Идентичность значений: В смысле владения отдельными значениями скорость и newSpeed
  имеют отдельные идентификаторы. Даже когда их значения равны, это разные
  переменные.

  .. code-block:: d

        int speed = 123;
        int newSpeed = speed;
        assert(speed == newSpeed);
        speed = 200;
        assert(speed != newSpeed);

.. index::
    оператор &

**Оператор  & (Получение адреса)**

До сих пор мы использовали оператор & с read(). Оператор & сообщает
readf(), куда поместить входные данные.
*Примечание: Как мы видели при чтении главы "Стандартный ввод" (стр. 15),
readf() также может использоваться без явных указателей.*
Адреса переменных также могут использоваться для других целей. Следующий
код просто выводит адреса двух переменных:

.. code-block:: d

    int speed = 123;
    int newSpeed = speed;
    writeln("speed : ", speed, " address: ", &speed);
    writeln("newSpeed: ", newSpeed, " address: ", &newSpeed);

переменные  speed и newSpeed одинаковое значение, разные адреса

.. code-block:: console

    speed : 123 address: 7FFF4B39C738
    newSpeed: 123 address: 7FFF4B39C73C

*Примечание: Это нормально, что адреса имеют разные значения при каждом запуске программы.
Переменные хранятся в тех частях памяти, которые оказываются доступными во время данного
конкретного выполнения программы*

Адреса обычно печатаются в шестнадцатеричном формате.
Кроме того, тот факт, что два адреса находятся на расстоянии 4 друг от друга, указывает на то, что эти два
целых числа расположены рядом друг с другом в памяти. (Обратите внимание, что значение
шестнадцатеричного C равно 12, поэтому разница между 8 и 12 равна 4.)

.. index::
    cсылочные переменные

Ссылочные переменные
--------------------

Терминология: До сих пор мы использовали фразу to provide access to (обеспечить доступ) в
нескольких контекстах на протяжении всей книги. Например, срезы и ассоциативные массивы
не владеют никакими элементами, но предоставляют доступ к элементам, которые принадлежат среде выполнения D
runtime. Другая фраза, идентичная по смыслу, - это "быть ссылкой на", как в фразе
"slices are references of zero or more elements", которая иногда используется даже
короче, как "этот срез ссылается на два элемента". Наконец, акт
доступа к значению через ссылку называется разыменованием.
Ссылочные переменные - это переменные, которые действуют как псевдонимы других переменных.
Хотя они выглядят и используются как переменные, у них нет значений сами.
Изменения, внесенные в ссылочную переменную, изменяют значение фактической переменной.
До сих пор мы уже использовали ссылочные переменные в двух контекстах:

* *ref в циклах foreach:* Ключевое слово ref делает переменную цикла фактическим
  элементом, соответствующим данной итерации. Когда ключевое слово ref не используется,
  переменная цикла является копией реального элемента.
  Это можно продемонстрировать и с помощью оператора &. Если их адреса
  одинаковые, две переменные будут ссылаться на одно и то же значение (или один и тот же
  элемент в данном случае):

.. code-block:: d

    int[] slice = [ 0, 1, 2, 3, 4 ];
    foreach (i, ref element; slice)
    {
        assert(&element == &slice[i]);
    }

Хотя это отдельные переменные, тот факт, что адреса элемента
и slice[i] одинаковы, доказывает, что они имеют одинаковую идентичность значений.
Другими словами, element и slice[i] - это ссылки на одно и то же значение.
Изменение любой из них влияет на фактическое значение. Следующая схема памяти
показывает снимок итерации, когда i равно 3:


.. image:: images/slice1_4_34.2_1.png

* *параметры функции ref и out:* Параметры функции, указанные как
  ref или out, являются псевдонимами фактической переменной, с помощью которой вызывается функция.
  Следующий пример демонстрирует этот случай, передавая одну и ту же переменную
  для разделения параметров ref и out функции. Опять же, оператор &
  указывает, что оба параметра имеют одинаковое значение identity:

.. code-block:: d

    import std.stdio;

    void main()
    {
        int originalVariable;
        writeln("address of originalVariable: ", &originalVariable);
        foo(originalVariable, originalVariable);
    }

    void foo(ref int refParameter, out int outParameter)
    {
        writeln("address of refParameter : ", &refParameter);
        writeln("address of outParameter : ", &outParameter);
        assert(&refParameter == &outParameter);
    }


.. code-block:: console

    address of originalVariable: 7FFF24172958
    address of refParameter : 7FFF24172958
    address of outParameter : 7FFF24172958

.. index::
    cсылочные типы

Ссылочные типы
--------------

Переменные ссылочных типов имеют индивидуальные идентификаторы, но у них нет
индивидуальных значений. Они предоставляют доступ к существующим переменным.
Мы уже рассматривали эту концепцию с помощью срезов. Срезы не владеют элементами, они
предоставляют доступ к существующим элементам:

.. code-block:: d

    void main()
    {
        // Although it is named as 'array' here, this variable is
        // a slice as well. It provides access to all of the
        // initial elements:
        int[] array = [0, 1, 2, 3, 4];
        // A slice that provides access to elements other than the
        // first and the last:
        int[] slice = array[1 .. $ - 1];
        // At this point slice[0] and array[1] provide access to
        // the same value:
        assert(&slice[0] == &array[1]);
        // Changing slice[0] changes array[1]:
        slice[0] = 42;
        assert(array[1] == 42);
    }


В отличие от ссылочных переменных, ссылочные типы - это не просто псевдонимы. Чтобы увидеть это
различие, давайте определим другой срез как копию одного из существующих срезов:

.. code-block:: d

    int[] slice2 = slice;

Два фрагмента имеют свои собственные адреса. Другими словами, у них разные
идентификаторы (?????):

.. code-block:: d

    assert(&slice != &slice2);

Следующий список представляет собой краткое изложение различий между ссылочными переменными
и ссылочными типами:
* Ссылочные переменные не имеют идентификаторов, они являются псевдонимами существующих
переменных.
* Переменные ссылочных типов имеют идентификаторы, но они не являются владельцами значений; скорее,
они предоставляют доступ к существующим значениям.
То, как slice и slice2 хранятся в памяти, можно проиллюстрировать, как показано на следующем
рисунке:

.. image:: images/speed_newspeed_34.1_3.png

Выделены три элемента, на которые ссылаются оба фрагмента.
Одно из различий между C++ и D заключается в том, что классы являются ссылочными типами в
D. Хотя мы подробно рассмотрим классы в последующих главах, ниже
приведен краткий пример, демонстрирующий этот факт:

.. code-block:: d

    class MyClass {
        int member;
    }

Объекты класса создаются с помощью ключевого слова new:

.. code-block:: d

    auto variable = new MyClass;

переменная является ссылкой на анонимный объект MyClass, который был
созданный с помощью new:

.. image:: images/34.3_2.png

Как и в случае сj chtpfvb, когда переменная копируется, копия становится еще одной
ссылкой на тот же объект. Копия имеет свой собственный адрес:

.. code-block:: d

    auto variable = new MyClass;
    auto variable2 = variable;
    assert(variable == variable2);
    assert(&variable != &variable2);

Они равны с точки зрения ссылки на один и тот же объект, но это
отдельные переменные:

.. image:: images/34.3_3.png


Это также можно показать, изменив член объекта:

.. code-block:: d

    auto variable = new MyClass;
    variable.member = 1;
    auto variable2 = variable; // They share the same object
    variable2.member = 2;
    assert(variable.member == 2); // The object that variable
    // provides access to has changed.

Еще один тип ссылок - ассоциативные массивы. Подобно срезам и классам, когда
ассоциативный массив копируется или присваивается другой переменной, оба дают доступ к
одному и тому же набору элементов:

.. code-block:: d

    string[int] byName =
    [
        1 : "one",
        10 : "ten",
        100 : "hundred",
    ];
    // The two associative arrays will be sharing the same
    // set of elements
    string[int] byName2 = byName;
    // The mapping added through the second ...
    byName2[4] = "four";
    // ... is visible through the first.
    assert(byName[4] == "four");

Как будет объяснено в следующей главе, разделение элементов не происходит, если
исходный ассоциативный массив изначально был null.

**Разница в операции присваивания**

В случае типов значений и ссылочных переменных операция присваивания изменяет
фактическое значение:

.. code-block:: d

    void main()
    {
        int number = 8;
        halve(number); // The actual value changes
        assert(number == 4);
    }

    void halve(ref int dividend)
    {
        dividend /= 2;
    }

С другой стороны, в ссылочных типах операция присваивания изменяет то, к какому
к какому значению осуществляется доступ. Например, присваивание переменной slice3, приведенной ниже
не изменяет значение какого-либо элемента; скорее, оно изменяет, на какие элементы
slice3 теперь является ссылкой:

.. code-block:: d

    int[] slice1 = [ 10, 11, 12, 13, 14 ];
    int[] slice2 = [ 20, 21, 22 ];
    int[] slice3 = slice1[1 .. 3]; // Access to element 1 and
    // element 2 of slice1
    slice3[0] = 777;
    assert(slice1 == [ 10, 777, 12, 13, 14 ]);
    // This assignment does not modify the elements that
    // slice3 is providing access to. It makes slice3 provide
    // access to other elements.
    slice3 = slice2[$ - 1 .. $]; // Access to the last element
    slice3[0] = 888;
    assert(slice2 == [ 20, 21, 888 ]);

Давайте продемонстрируем тот же эффект на примере двух объектов типа MyClass:

.. code-block:: d

    auto variable1 = new MyClass;
    variable1.member = 1;
    auto variable2 = new MyClass;
    variable2.member = 2;
    auto aCopy = variable1;
    aCopy.member = 3;
    aCopy = variable2;
    aCopy.member = 4;
    assert(variable1.member == 3);
    assert(variable2.member == 4);

Приведенная выше переменная aCopy сначала ссылается на тот же объект, что и variable1, а затем
на тот же объект, что и variable2. Как следствие, член .member, который изменяется
через aCopy сначала принадлежит variable1, а затем variable2.

**Переменные ссылочного типа не могут ссылаться на какой-либо объект**

У ссылочной переменной всегда есть фактическая переменная, псевдонимом которой она является.
не может начать свою жизнь без переменной. С другой стороны, переменные ссылочного
типа могут начать свою жизнь без ссылки на какой-либо объект.
Например, переменная MyClass может быть определена без реального объекта
не был создан с помощью new:

.. code-block:: d

    MyClass variable;

Такие переменные имеют специальное значение null. Мы рассмотрим null и ключевое слово is
в одной из следующих глав (стр. 233).

Массивы фиксированной длины являются типами значений, срезы - ссылочными типами
--------------------------------------------------------------------------------

Массивы и срезы в языке D расходятся, когда речь заходит о типе значения и ссылочном типе.
Как мы уже видели выше, срезы являются ссылочными типами. С другой стороны,
массивы фиксированной длины - это типы значений. Они владеют своими элементами и ведут себя как
отдельные значения:

.. code-block:: d

    int[3] array1 = [ 10, 20, 30 ];
    auto array2 = array1; // элементы массива array2 отличаются от элементов массива array1
    // from array1's
    array2[0] = 11;
    // First array is not affected:
    assert(array1[0] == 10);

array1 - это массив фиксированной длины, поскольку его длина задается при его определении.
определен. Поскольку auto заставляет компилятор сделать вывод о типе массива array2,
он также является массивом фиксированной длины. Значения элементов массива2 копируются из значений
элементов массива1. Каждый массив имеет свои собственные элементы. Изменение
элемента в одном из них не влияет на другой.

Эксперимент
-----------

Следующая программа представляет собой эксперимент по применению оператора == к различным
типам. Она применяет оператор как к переменным определенного типа, так и к
адресам этих переменных. Программа выдает следующий результат:

.. code-block:: console

    Type of variable a == b &a == &b
    ===========================================================================
    variables with equal values (value type)                true   false
    variables with different values (value type)            false  false
    foreach with 'ref' variable                             true   true
    foreach without 'ref' variable                          true   false
    function with 'out' parameter                           true   true
    function with 'ref' parameter                           true   true
    function with 'in' parameter                            true   false
    slices providing access to same elements                true   false
    slices providing access to different elements           false  false
    MyClass variables to same object (reference type)       true   false
    MyClass variables to different objects (reference type) false  false

Приведенная выше таблица была создана с помощью следующей программы:

.. code-block:: d

    import std.stdio;
    import std.array;

    int moduleVariable = 9;

    class MyClass
    {
        int member;
    }

    void printHeader()
    {
        immutable dchar[] header = " Type of variable" ~ " a == b &a == &b";
        writeln();
        writeln(header);
        writeln(replicate("=", header.length));
    }

    void printInfo(const dchar[] label,
        bool valueEquality,
        bool addressEquality)
    {
        writefln("%55s%9s%9s", label, valueEquality, addressEquality);
    }

    void main()
    {
        printHeader();
        int number1 = 12;
        int number2 = 12;
        printInfo("variables with equal values (value type)", number1 == number2, &number1 == &number2);
        int number3 = 3;
        printInfo("variables with different values (value type)", number1 == number3, &number1 == &number3);
        int[] slice = [4];
        foreach (i, ref element; slice)
        {
            printInfo("foreach with 'ref' variable", element == slice[i], &element == &slice[i]);
            foreach (i, element; slice)
            {
                printInfo("foreach without 'ref' variable",
                    element == slice[i],
                    &element == &slice[i]);
            }
            outParameter(moduleVariable);
            refParameter(moduleVariable);
            inParameter(moduleVariable);
            int[] longSlice = [5, 6, 7];
            int[] slice1 = longSlice;
            int[] slice2 = slice1;
            printInfo("slices providing access to same elements",
                slice1 == slice2,
                &slice1 == &slice2);
            int[] slice3 = slice1[0 .. $ - 1];
            printInfo("slices providing access to different elements",
                slice1 == slice3,
                &slice1 == &slice3);
            auto variable1 = new MyClass;
            auto variable2 = variable1;
            printInfo("MyClass variables to same object (reference type)", variable1 == variable2, &variable1 == &variable2);
            auto variable3 = new MyClass;
            printInfo("MyClass variables to different objects (reference type)", variable1 == variable3, &variable1 == &variable3);
        }

        void outParameter(out int parameter)
        {
            printInfo("function with 'out' parameter", parameter == moduleVariable, &parameter == &moduleVariable);
        }

        void refParameter(ref int parameter)
        {
            printInfo("function with 'ref' parameter", parameter == moduleVariable, &parameter == &moduleVariable);
        }

        void inParameter(in int parameter)
        {
            printInfo("function with 'in' parameter", parameter == moduleVariable, &parameter == &moduleVariable);
        }

Примечания:

* Программа использует переменную модуля при сравнении различных типов
  параметров функций. Переменные модуля определяются на уровне модуля, вне
  всех функций. Они глобально доступны для всего кода в
  модуля.
* Функция replicate() модуля std.array берет массив (строка "="
  строка выше) и повторяет его указанное количество раз.

Итоги
-----

* Переменные типа value имеют свои собственные значения и адреса.
* Ссылочные переменные не имеют собственных значений и адресов. Они являются
  псевдонимами существующих переменных.
* Переменные ссылочных типов имеют собственные адреса, но значения, на которые они ссылаются, не принадлежат им.
* В ссылочных типах присваивание не изменяет значение, оно изменяет то, к какому значению
  к какому значению осуществляется доступ.
* Переменные ссылочных типов могут быть null.
